#include "EffectTemplatePCH.h"
#include "Effect.h"
#include "EffectManager.h"

static EffectManager* g_pEffectManager = NULL;

EffectManager& EffectManager::Create()
{
    if ( g_pEffectManager == NULL ) g_pEffectManager = new EffectManager();
    return *g_pEffectManager;
}

void EffectManager::Destroy()
{
    if ( g_pEffectManager != NULL )
    {
        delete g_pEffectManager;
        g_pEffectManager = NULL;
    }
}

EffectManager& EffectManager::Get()
{
    assert( g_pEffectManager != NULL );
    return *g_pEffectManager;
}

EffectManager::EffectManager()
: m_bIsInitialized( false )
, m_cgContext( NULL )
{

}

EffectManager::~EffectManager()
{
    Terminate();
}

bool EffectManager::Initialize()
{
    if ( m_bIsInitialized ) return true;

    // Register the error handler
    cgSetErrorHandler( EffectManager::ErrorHandler, this );

    // Create the Cg context.
    m_cgContext = cgCreateContext();

    // Register the default state assignments for OpenGL.
    // See the documentation on CgFX States in the Cg Reference Manual.
    cgGLRegisterStates( m_cgContext );
    // This will allow the Cg runtime to manage texture binding.
    cgGLSetManageTextureParameters( m_cgContext, CG_TRUE );

    // TODO: Create shared parameters with specific semantics so that 
    // any effect that defines a parameter with the same semantic will be
    // automatically linked to the shared parameter.

    m_bIsInitialized = true;
    return true;
}

void EffectManager::Terminate()
{
    // Delete all the effects
    EffectMap::iterator effectIter = m_Effects.begin();
    while ( effectIter != m_Effects.end() )
    {
        delete (effectIter->second);
        ++effectIter;
    }
    m_Effects.clear();

    if ( m_cgContext != NULL )
    {
        cgDestroyContext( m_cgContext );
        m_cgContext = NULL;
    }

    cgSetErrorHandler( NULL, NULL );

    m_bIsInitialized = false;
}

void EffectManager::ErrorHandler( CGcontext context, CGerror error, void* appData )
{
    if ( error != CG_NO_ERROR )
    {
        const char* pStr = cgGetErrorString(error);
        std::string strError = ( pStr == NULL ) ? "" : pStr;
        std::cerr << "EffectManager::ErrorHandler: " << strError << std::endl;

        std::string strListing;
        if ( error == CG_COMPILER_ERROR )
        {
            pStr = cgGetLastListing( context );
            strListing = ( pStr == NULL ) ? "" : pStr;

            std::cerr << strListing << std::endl;
        }

        // Invoke the event to notify any subscribers.
        EffectManager* pEffectManager = static_cast<EffectManager*>( appData );
        RuntimeErrorEventArgs eventArgs( *pEffectManager, error, strError, strListing );
        pEffectManager->OnRuntimeError( eventArgs );
    }
}

Effect& EffectManager::CreateEffectFromFile( const std::string& effectFile, const std::string& effectName )
{
    assert( m_bIsInitialized );
    // first check if the file is valid
    if ( !fs::exists( effectFile ) || !fs::is_regular_file(effectFile) || fs::file_size(effectFile) == 0 )
    {
        std::cerr << "EffectManager::CreateEffectFromFile: Invalid file: \"" << effectFile << "\"" << std::endl;
        return Effect::InvalidEffect;
    }

    Effect* pEffect = NULL;

    // Look for the effect with the same name in the effect map if it already exists
    EffectMap::iterator effectIter = m_Effects.find( effectName );
    if ( effectIter == m_Effects.end() )
    {
        // Load a new effect.
        pEffect = new Effect( m_cgContext, effectFile, effectName );

        EffectLoadedEventArgs effectLoadedEventArgs( *this, *pEffect );
        OnEffectLoaded( effectLoadedEventArgs );

        // Bind the event in the case the effect gets reloaded
        pEffect->EffectLoaded += boost::bind( &EffectManager::OnEffectLoaded, this, _1 );

        m_Effects.insert( EffectMap::value_type( effectName, pEffect ) );
    }
    else
    {
        pEffect = effectIter->second;
        if ( pEffect->m_EffectFile != effectFile )
        {
            // Replace an existing effect with a new one.
            std::cerr << "EffectManager::CreateEffectFromFile: An effect with name \"" << effectName << "\" was found in the effect map." << std::endl;
            std::cerr << "    This effect will be replaced with the new effect: \"" << effectFile << "\"" << std::endl;
            pEffect->LoadEffectFromFile( effectFile );
        }
    }
    
    return *pEffect;
}

Effect& EffectManager::GetEffect( const std::string& effectName )
{
    EffectMap::iterator effectIter = m_Effects.find( effectName );
    if ( effectIter == m_Effects.end() )
    {
        std::cerr << "EffectManager::GetEffectByName: No effect with name \"" << effectName << "\" was found." << std::endl;
        return Effect::InvalidEffect;
    }
    return *(effectIter->second);
}

void EffectManager::ReloadEffects()
{
    EffectMap::iterator effectIter = m_Effects.begin();
    while ( effectIter != m_Effects.end() )
    {
        (effectIter->second)->CheckForReload();
        ++effectIter;
    }
}

void EffectManager::OnRuntimeError( RuntimeErrorEventArgs& e )
{
    RuntimeError( e );
}

void EffectManager::OnEffectLoaded( EffectLoadedEventArgs& e )
{
    EffectLoaded( e );
}